#include "RecoveryManager.h"

namespace miniprojeto {
namespace recovery {

RecoveryManager::RecoveryManager() {
	redundancyFile = "";
	failures = 0;
	blocksList = new Lista_Cresc<CRCBlocks*>();
	
	if (sem_init(&semWaitWorkers, 0, 0) < 0) {
		cerr<<"Erro iniciando o semafaro Works"<<endl;
	}
	if (sem_init(&mutex, 0, 1) < 0) {
		cerr<<"Erro iniciando o Mutex"<<endl;
	}
}

RecoveryManager::~RecoveryManager() {
	sem_destroy	(&semWaitWorkers);
	sem_destroy (&mutex);
	blocksList->ApagaLista();
	delete blocksList;
}

void RecoveryManager::setTargetFile(string fName) {
	this->redundancyFile = fName;
}
void RecoveryManager::init() {
	if (this->redundancyFile == "") {
		string temp = "RecoveryManager::init() ->  ";
		temp += "redudancyFile == " + redundancyFile + "\n";
		InitializationException except(temp);
		throw except;
	}
	
	openFiles();
	
	for (int i = 0; i < blocksCount;) {
		CRCBlocks* block;
		CRCWorker* worker;
		int nWorkers = blocksCount - i;
		if (nWorkers > 4) {
			nWorkers = 4;	
		}
		i += nWorkers;
		this->setWorkersCreated(nWorkers);
		for (int j = 0; j < nWorkers; j++) {
			block = new CRCBlocks(); /*aloca bloco*/
			block->readFile(in);
			worker = new CRCWorker(); /*aloca worker*/
			worker->setCRCBlock(block);
			worker->addFinishListener(this);
			worker->setNumber(j+1);
			worker->start();
		} 
		
		this->waitWorkersFinished();
		
		if (hasFailures()) {
			cerr<<"Número de falhas identificadas: "<<failures<<endl;
			if (isRecuperable()) {
				recovery();	
			} else {
				cerr<<"Arquivo severamente danificado!"<<endl;
				cerr<<"Recuperação não travial."<<endl;
				exit(0);	
			}
		} else {
			
			ignoreXorBlock();	
		}
		
		writeBlocks();
		
	}
	out.close();
	in.close();
		
}

void RecoveryManager::openFiles() {
	char* buffer = new char[256];
	in.open(redundancyFile.c_str(), ios::binary );
	
	in.read(buffer, 8);					/*8 bytes*/
	buffer[8] = '\0';
	cout<<buffer<<endl;
	if (strcmp(buffer, "REDUFILE") != 0) {
		cerr<<"Formato de Arquivo não reconhecido.";
		exit(1);	
	}
	unsigned short nameLen;
	in.read ((char*) &nameLen, sizeof(short));
	cout<<nameLen<<endl;
	in.read(buffer, nameLen);
	buffer[nameLen] = '\0'; /*nome do arquivo de saida*/
	outputFile = buffer;
	outputFile += ".2";
	cout<<outputFile<<endl;
	in.read((char*) &blocksCount, sizeof(int));
	cout<<blocksCount<<endl;
	in.read((char*) &fileLen, sizeof(long));
	cout<<fileLen<<endl;
	in.read(buffer, 25);
	cout<<buffer<<endl;

	out.open(outputFile.c_str(), ios::binary );
}

void RecoveryManager::setWorkersCreated(int nWorkers) {
	sem_wait(&mutex);
	this->waitingWorkers = nWorkers;
	sem_post(&mutex);	
}

void RecoveryManager::recevFinishWorkNotification(void* worker) {
	sem_wait(&mutex);
	CRCWorker* crcWorker = (CRCWorker*) worker; /*cast*/
	CRCBlocks* block = crcWorker->getCRCBlocks();
	int index = block->getNumber();
	if (!(block->getReminder() == 0)) {
		this->failures++;
		this->corrompedBlockIndex = index;
	} else {
		blocksList->Insere(block);
	}
	waitingWorkers--;
	delete crcWorker;
	if (waitingWorkers == 0) {
		sem_post(&semWaitWorkers);
	}
	sem_post(&mutex);	
}

void RecoveryManager::waitWorkersFinished() {
	sem_wait(&semWaitWorkers);
	/*este método apenas espera todas as threads terminarem*/
}

bool RecoveryManager::hasFailures() {
	return (failures > 0);
}

bool RecoveryManager::isRecuperable() {
	return (failures <= 1);
}

void RecoveryManager::recovery() {
	CRCBlocks* xorBlock = new CRCBlocks(NULL, 0);
	xorBlock->readFile(in);
	blocksList->Insere(xorBlock);
	
	CRCBlocks* block;
	XORByteArray* xorA =  new XORByteArray(BLOCK_SIZE + 2);
	xorA->addByteArray(xorBlock->getBytes());
	
	if (!blocksList->Vazia()) {
		blocksList->Primeiro(block);
		xorA->addByteArray(block->getBytes());
		while (blocksList->Proximo(block)) {
			xorA->addByteArray(block->getBytes());
		}
	}
	unsigned char* resultXor = xorA->evaluateXor();
	CRCBlocks* newBlock = new CRCBlocks(resultXor);
	newBlock->setNumber(corrompedBlockIndex);
	blocksList->Insere(newBlock);
	delete xorA;
	delete xorBlock;
}

void RecoveryManager::ignoreXorBlock() {
	in.seekg(BLOCK_SIZE, ios_base::cur);
	unsigned short reminder;
	in.read((char*) &reminder, sizeof(short));
}

void RecoveryManager::writeBlocks() {
	static unsigned long writedBytes = 0;
	if (!(blocksList->Vazia())) {
		CRCBlocks* block;
		unsigned long nextLen = fileLen - writedBytes;
		if (nextLen > BLOCK_SIZE) {
			nextLen = BLOCK_SIZE;	
		}
		blocksList->Primeiro(block);
		unsigned char* buffer = block->getBytes();
		out.write((char*) buffer, nextLen);
		delete block;
		writedBytes += nextLen;
		while (blocksList->Proximo(block)) {
			unsigned long nextLen = fileLen - writedBytes;
			if (nextLen > BLOCK_SIZE) {
				nextLen = BLOCK_SIZE;	
			}
			unsigned char* buffer = block->getBytes();
			out.write((char*) buffer, nextLen);
			delete block;
			writedBytes += nextLen;
		}
	}
	blocksList->ApagaLista();
	failures = 0; /*limpa contador de falhas*/
}

}
}
